<?php

	error_reporting(CORMO_ERROR);

	require_once 'Access.class.php';
    require_once 'Object.class.php';
    require_once 'Message.class.php';
    require_once 'Debug.class.php';

    /**
     * Abstraccion, que nos permite Administrar la Persistencia de cada uno de los
     * Objetos del Modelo de Negogio. Cada objeto de Negocio que Herede de esta Class
     * con unos pocos pasos de configuracion podra contar con todos los metodos necesarios
     * para poder administrar la Persistencia del mismo sin tener que escribir codigo.
     * Pero pudiendo a su vez personalizar dicha Persistencia.
     *
     * @category cormo
     * @package Core
     * @author Germán Dario Peraferrer <gperaferrer@gmail.com>
     * @copyright Copyright (c) 2007 Germán Dario Peraferrer - Beetec
     * @version 1.0 Beta
     * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
     * @link http://code.google.com/p/cormoframework/
     * @link http://www.beetec.com.ar
     */
    class Persist {

        /**
        * Composicion: Instacia de Access
        *
        * @var Access
        */
        private $cnxPDO;

        /**
        * Composicion: Instancia de Message
        *
        * @var Message
        */
        private $Message;

        /**
        * Composicion: creamos un objeto Config para poder manipular el objeto a Persistir
        *
        * @var Config
        */
        private $config;

        /**
        * Creamos el Objeto de Persistencia el cual sera utilizado para poder
        * crear los Objetos del Modelo del Negocio y administrar su Persistencia.
        *
        * @return void
        */
        public function __construct($nameObjectORM) {
            Debug::captureBackTrace();
            $this->Message = new Message();
            $this->setConfig(new Object($nameObjectORM));
            if ($this->getConfig()->isConfigValid())	{
                try  {
                    // Recuperamos la Instacia de la Conexion a la Base de Datos
                    $this->setConnection(new Access($this->getConfig()->getConnection()));

                    // Inicilizamos todos los property con valor NULL debido a que se esta creando el objeto
                    foreach ($this->getConfig()->getConfigFieldsNotKey() as $property)	{
                        $this->{'set'.$property}(null);
                    }
        	    }  catch(PDOException $e)  {
                    die("Persist Error: No se ha podido crear el Objeto PDO.<br>".$e->getMessage());
                }
            } else	{
                $this->Message()->setMessage("Persist: Error la configuracion de ORM del Objeto a Persistir no se encuentra completa.");
            }
        }

        /**
        * Muestra el contenido Completo del Obtejo en formato Texto
        *
        * @return string
        */
        public function __toString() {
            $stringReturn = '';

            foreach($this->getConfig()->getConfigFields() as $key => $value) {
                $stringReturn .= $value.' = '.$this->{'get'.$value}().', ';
            }
            // Eliminamos la ultima coma
            $stringReturn = substr($stringReturn, 0, strlen($stringReturn)-2);

            return $stringReturn;
        }

        /**
        * Este metodo es utilizado, generalmente, si vmaos a Serializar un Objeto, porque
        * PHP lo llamara antes de comenzar a Serializarlo, esto nos puede ayudar a liverar
        * recursos y demas.
        *
        */
        public function __sleep()	{

        }

       /**
        * Este metodo es utilizado, generalmente, cuando volvemos a generar un Objeto que se encontraba
        * Serializado, es decir, que podriamos utilizarla como funcion de reinicio, debido a que PHP
        * la llamara en primera instancia.
        *
        */
        public function __wakeup() {

        }

        /**
        * Muestra el Contenido del Objeto en un Array
        *
        * @return array
        */
        public function toArray() {
            $arrReturn = array();

            foreach($this->getConfig()->getConfigFields() as $key => $value) {
                $arrReturn[$value] = $this->{'get'.$value}();
            }

            return $arrReturn;
        }

        /**
        * Carga los valores de un Array en el Objeto, de uso particular interno, el uso de dicha funcion puede causar inconsistencias.
        * Queda bajo su responsabilidad la implementaci�n de la misma.
        *
        * @param Array $arr
        */
        public function loadArray($arr) {
            foreach($arr as $field => $value) {
                $this->{'set'.$field}($value);
            }
        }

        /**
        * Guardamos la Instancia de la Conexion a la Base de Datos.
        *
        * @param PDO $pdo
        * @return void;
        */
        private function setConnection($pdo)  {
            $this->cnxPDO = $pdo;
        }

        /**
        * Obtenemos la instacia de la Conexion PDO a la Base de Datos.
        *
        * @return Access
        */
        public function getConnection()  {
            return $this->cnxPDO;
        }

        /**
        * Obtenenemos la configuracion del ORM para este Objeto
        *
        * @return Object
        */
        public function getConfig() {
            return $this->config;
        }

        /**
        * Define la configuraci�n del Object actual
        *
        * @param Object $config
        */
        private function setConfig(Object $config) {
            $this->config = $config;
        }

        /**
        * Devuelve el la Instacia del Manejador de Mensajes para Persist
        *
        * @return Message
        */
        public function Message()	{
            return $this->Message;
        }

        /**
        * Obtiene elUltimo Mensaje de Persist y se implementa para que las Class
        * que heredan de Persist puedan obtene el Ultimo Mensaje.
        *
        * @return string
        */
        public function getMessage()	{
            return $this->Message()->getMessage();
        }

        /**
        * Busca los datos del Objeto en la Base de Datos y hace un matecho de los campos
        * que se han especificado en el __contruct de la Class que nos ha Heredado.
        *
        * @param int $id
        * @return boolean
        */
        protected function find($id) {
            // Verificamos si el Objeto PDO se ha creado durante la conexion
            Debug::captureBackTrace();

            /**
             * FIXME: Bug al utilizar multiples DB !!!
             *
             * Parche: volvemos a cargar el Driver al PDO
             */
            $this->setConnection(new Access($this->getConfig()->getConnection()));

            if(Access::isConnected())  {

                // Virificamos sitenemos id para buscar en caso de ser null no hacemos nada
                if ($id != null)	{
                    try {
                        /*
                        * Preparamos el SELECT para realizar la busqueda de los campos configurados para
                        * la Class que nos ha Heredado.
                        */
                        $sql = 'SELECT';
                        foreach($this->getConfig()->getConfigFields() as $key => $value) {
                            // Verificamos si el field actual debe utilizar alguna function particular de SQL
                            if (array_key_exists($value, $this->getConfig()->getFunctionsSQL())) {
                                // Obtenemos la function que debe ser utilizada junto a este Field
                                $arr = $this->getConfig()->getFunctionsSQL();
                                $tmpFunction = $arr[$value];
                                $sql .= ' '.$tmpFunction.' as '.$value.',';
                            } else {
                                $sql .= ' '.$this->getConfig()->getConfigTableName().'.'.$value.',';
                            }
                        }

                        // Eliminamos la Ultima Coma
                        $sql = substr($sql, 0, strlen($sql)-1);

                        /*
                        * Preparamos el FROM del SELECT que se utilizara para ubicar el registro que posee
                        * los valores que le seran asignados a la Class que nos ha Heredado.
                        */
                        $sql .= ' FROM '.$this->getConfig()->getConfigTableName();

                        /*
                        * Preparamos el WHERE del SELECT que utilizaremos para localizar los valores del
                        * Class que nos ha HEredado.
                        */
                        $sql .= ' WHERE ('.$this->getConfig()->getConfigTableName().'.'.$this->getConfig()->getConfigKey().' = :id)';

                        // Preparamos la Consulta
                        $sth = $this->getConnection()->prepare($sql);
                        $sth->bindValue(':id', $id);

                        // Ejecutamos la consulta
                        if($sth->execute()) {

                            // Verificamos si se ha localizado el Objeto Persistido
                            if($sth->columnCount() > 0) {
                                /*
                                * Convertimos el resultado de la consulta en un Objeto stdClass para luego obtener
                                * los valores de cada uno de sus Atributos y asi poder asignarlos a cada uno de los
                                * atributos del Objeto que nos ha Heradado, pero utilizando los Getters y Setters.
                                */
                                $obj = $sth->fetch(PDO::FETCH_OBJ);

                                // Recorremos el Objeto stdClass e invocamos a los Setters correspondientes
                                foreach($obj as $key => $value) {
                                    $this->{'set'.$key}($value);
                                }

                                // Retornamos el Key del Registro que se busco
                                //return $this->{'get'.$this->getConfigData("Key")}();
                                return true;

                            } else  { // No se ha encontrado el Key buscado
                                $this->Message()->setMessage('El ID de Objeto no se ha localizado.');
                                return false;
                            }

                        } else {  // Error en la ejecucion de la consulta a la DB y por lo tanto de devuelve el Error de la misma.
                            $this->Message()->setMessage('<pre>'.print_r($sth->errorInfo(), true).'</pre>');
                            return false;
                        }

                    } catch(PDOException $e)  {
                        $this->Message()->setMessage("Persist [".get_class($this)."] - ERROR: " . $e->getMessage() . "<br/>");
                        return false;
                    }

                } else	{  // id == null
                    $this->Message()->setMessage("Persist Error: No se realiza la busqueda id == null.");
                    return false;
                }

            } else {
                $this->Message()->setMessage("Persist Error: No estamos conectado con la Base de Datos.");
                return false;
            }
        }

        /**
        * Verifica si el Objeto que nos haredado ya se encuentra persitido, es decir,
        * si ya esta en la base de datos, esto lo realizamos para poder mantener la integridad
        * de los datos a la hora de utilizar el metodo SAVE. o implementamos asi, debido a
        * que no todo los DBMS implementan Registros duplicados por valores no Key.
        *
        * @return int
        */
        protected function exists() {
            Debug::captureBackTrace();

            /**
             * FIXME: Bug al utilizar multiples DB !!!
             *
             * Parche: volvemos a cargar el Driver al PDO
             */
            $this->setConnection(new Access($this->getConfig()->getConnection()));

            if(Access::isConnected())  {

                try {
                    // Creamos el SELECT con el ID para luego ser retornado en caso de que el Objeto exista
                    $sql = 'SELECT '.$this->getConfig()->getConfigTableName().'.'.$this->getConfig()->getConfigKey();

                    // Especificamos en que tabla se buscaran los datos
                    $sql .= ' FROM '.$this->getConfig()->getConfigTableName();

                    // Armamos la condicion con todos los campos excepto el Key de la Tabla
                    $sql .= ' WHERE ';

                    // Creamos los campos para el WHERE con su indicadores de Parametro
                    foreach($this->getConfig()->getPropertyCompare() as $key => $value) {
                        // Verificamos si el field actual debe utilizar alguna function particular de SQL
                        if (array_key_exists($value, $this->getConfig()->getFunctionsSQL())) {
                            // Obtenemos la function que debe ser utilizada junto a este Field
                            $arr = $this->getConfig()->getFunctionsSQL();
                            $tmpFunction = $arr[$value];
                            $sql .= ' ('.$tmpFunction.' = :'.$value.') AND ';
                        } else {
                            $sql .= '('.$this->getConfig()->getConfigTableName().'.'.$value.' = :'.$value.') AND ';
                        }
                    }

                    // Eliminamos el Ultimo AND
                    $sql = substr($sql, 0, strlen($sql)-5);

                    // Preparamos la Consulta
                    $sth = $this->getConnection()->prepare($sql);

                    // Completamos los Paramtros del WHERE con los datos que posee el objeto que nos heredo
                    foreach($this->getConfig()->getPropertyCompare() as $key => $value)	{
                        // Verificamos si se ha especificado valor a este Property
                        if(!is_null($this->{'get'.$value}())) {
                            $tmpValue = $this->{'get'.$value}();
                            $sth->bindValue(':'.$value, $tmpValue);
                        } else {
                            // Como no se especifico valor al property, buscamos si se le asigno un valor Default
                            if (!is_null($this->getConfig()->getConfigDefaultValues($value))) {
                                $tmpValue = $this->getConfig()->getConfigDefaultValues($value);
                                $sth->bindValue(':'.$value, $tmpValue);
                            } else {
                                $tmpValue = $this->{'get'.$value}();
                                $sth->bindValue(':'.$value, $tmpValue);
                            }
                        }
                    }

                    // Ejecutamos la consulta
                    if($sth->execute()) {
                        // Devolvemos el Key del Registro ya existente
                        $result = $sth->fetch();
                        return $result[$this->getConfig()->getConfigKey()];
                    } else {
                        $this->Message()->setMessage('Persist - Exists - Error: No se pudo ejecutar el INSERT.<br><pre>'.print_r($sth->errorInfo(), true).'</pre>');
                        return null;
                    }

                } catch(PDOException $e)  {
                    $this->Message()->setMessage("Persist [".get_class($this)."] - ERROR: " . $e->getMessage() . "<br/>");
                    return false;
                }

            } else {
                $this->Message()->setMessage("Persist Error: No estamos conectado con la Base de Datos.");
                return false;
            }
        }

        /**
        * Se encarga de guardar los datos del Objeto que nos ha Heredado, de manera
        * automatica detecta si debe hacer un UPDATE o INSERT, de esta manera estamos
        * desprocupados de que es lo que debemos hacer.
        *
        * @return bool or int (Cuando el Objeto se interpreta como nuevo, pero el mismo
        *                      ya Existe con otro Key, es por ello que se devuelve el
        *                      Key del Objeto existente)
        */
        protected function save() {
            Debug::captureBackTrace();

            /**
             * FIXME: Bug al utilizar multiples DB !!!
             *
             * Parche: volvemos a cargar el Driver al PDO
             */
            $this->setConnection(new Access($this->getConfig()->getConnection()));

            if(Access::isConnected())  {

                try {

                    // Determinamos si tenemos que hacer INSERT o UPDATE
                    if($this->{'get'.$this->getConfig()->getConfigKey()}() == null)  {

                        // Verificamos si los datos del objeto que no heredo ya existen
                        if(($id_existe = $this->exists()) == null) {
                            // INSERT
                            $sql = 'INSERT INTO '.$this->getConfig()->getConfigTableName().' (';

                            // Especificamos los campos en los cuales se insertaran datos
                            foreach($this->getConfig()->getConfigFieldsNotKey() as $key => $value) {
                                $sql .= $this->getConfig()->getConfigTableName().'.'.$value.', ';
                            }

                            // Eliminamos la Ultima Coma
                            $sql = substr($sql, 0, strlen($sql)-2);
                            $sql .= ') VALUES (';

                            // Creamos los Parametros en donde se colocaran los datos a Insertar
                            foreach($this->getConfig()->getConfigFieldsNotKey() as $key => $value) {
                                $sql .= ':'.$value.', ';
                            }

                            // Eliminamos la Ultima Coma
                            $sql = substr($sql, 0, strlen($sql)-2);
                            $sql .= ')';

                            // Preparamos la Consulta
                            $sth = $this->getConnection()->prepare($sql);

                            // Asignamos los valores de los Valueetros que se han creado para insertar datos
                            foreach($this->getConfig()->getConfigFieldsNotKey() as $key => $value)	{
                                // Verificamos si se ha especificado valor a este Property
                                if(!is_null($this->{'get'.$value}())) {
                                    $tmpValue = $this->{'get'.$value}();
                                    $sth->bindValue(':'.$value, $tmpValue);
                                } else {
                                    // Como no se especifico valor al property, buscamos si se le asigno un valor Default
                                    if (!is_null($this->getConfig()->getConfigDefaultValues($value))) {
                                        $tmpValue = $this->getConfig()->getConfigDefaultValues($value);
                                        $sth->bindValue(':'.$value, $tmpValue);
                                    } else {
                                        $tmpValue = $this->{'get'.$value}();
                                        $sth->bindValue(':'.$value, $tmpValue);
                                    }
                                }
                            }

                            $this->getConnection()->beginTransaction();

                            // Ejecutamos la consulta
                            if($sth->execute()) {
                                $this->getConnection()->commit();
                                // Recuperamos el Key del Objeto que se acaba de crear en la Base de Datos
                                $this->{'set'.$this->getConfig()->getConfigKey()}($this->exists());
                                return true;
                            } else {
                                $this->getConnection()->rollBack();
                                $this->Message()->setMessage('Persist - save - Error: No se pudo ejecutar el INSERT.<br>'.$sql.'<pre>'.print_r($sth->errorInfo(), true).'</pre>');
                                return false;
                            }

                        } else  { // El Objeto ya existe, se retornara el Key del mismo.
                            $this->Message()->setMessage('El Objeto ya existe, es por ello que no sera guardado.');
                            // Le asignamos el ID del objeto que ya existe persistido para que pueda seguir oprando con el
                            $this->{'set'.$this->getConfig()->getConfigKey()}($id_existe);
                            return false;
                        }

                    } else  {
                        // UPDATE
                        $sql = 'UPDATE '.$this->getConfig()->getConfigTableName().' SET ';

                        // Especificamos los campos en los cuales se actualizaran datos
                        foreach($this->getConfig()->getConfigFieldsNotKey() as $key => $value)
                        $sql .= $this->getConfig()->getConfigTableName().'.'.$value.' = :'.$value.', ';

                        // Eliminamos la Ultima Coma
                        $sql = substr($sql, 0, strlen($sql)-2);
                        $sql .= ' WHERE ('.$this->getConfig()->getConfigTableName().'.'.$this->getConfig()->getConfigKey().' = :id)';

                        // Preparamos la Consulta
                        $sth = $this->getConnection()->prepare($sql);

                        // Asignamos los valores de los Parametros que se han creado para insertar datos
                        foreach($this->getConfig()->getConfigFieldsNotKey() as $key => $value)	{
                            // Verificamos si se ha especificado valor a este Property
                            if(!is_null($this->{'get'.$value}())) {
                                $tmpValue = $this->{'get'.$value}();
                                $sth->bindValue(':'.$value, $tmpValue);
                            } else {
                                // Como no se especifico valor al property, buscamos si se le asigno un valor Default
                                if (!is_null($this->getConfig()->getConfigDefaultValues($value))) {
                                    $tmpValue = $this->getConfig()->getConfigDefaultValues($value);
                                    $sth->bindValue(':'.$value, $tmpValue);
                                } else {
                                    $tmpValue = $this->{'get'.$value}();
                                    $sth->bindValue(':'.$value, $tmpValue);
                                }
                            }
                        }

                        // Colocamos el Valor del Key para el WHERE
                        $tmpValue = $this->{'get'.$this->getConfig()->getConfigKey()}();
                        $sth->bindValue(':id', $tmpValue);

                        $this->getConnection()->beginTransaction();

                        // Ejecutamos la consulta
                        if($sth->execute()) {
                            $this->getConnection()->commit();
                            return true;
                        } else {
                            $this->getConnection()->rollBack();
                            $this->Message()->setMessage('Persist - save - Error: No se pudo ejecutar el UPDATE.<br>'.$sql.'<br><pre>'.print_r($sth->errorInfo(), true).'</pre>');
                            return false;
                        }

                    }
                } catch(PDOException $e)  {
                    $this->Message()->setMessage("Persist [".get_class($this)."] - ERROR: " . $e->getMessage() . "<br/>");
                    return false;
                }

            } else  {
                $this->Message()->setMessage("Persist Error: No estamos conectado con la Base de Datos.");
                return false;
            }
        }

        /**
        * Se encarga de Eliminar la Persistencia del Objeto Actual, de esta manera no se pierden los datos
        * del Objeto, si el mismo ya no esta Persistido en la DB.
        * Al Ejecutar este Metodo, el valor del Atributo Key del objeto se colocara en NULL para evitar
        * posibles Errores si se ejecutase el Metodo SAVE, en este caso el mismo asignaria un valor de Key
        * diferente al que poseia antes del DELETE.
        *
        * @return boolean
        */
        protected function delete() {
            Debug::captureBackTrace();

            /**
             * FIXME: Bug al utilizar multiples DB !!!
             *
             * Parche: volvemos a cargar el Driver al PDO
             */
            $this->setConnection(new Access($this->getConfig()->getConnection()));

            if(Access::isConnected())  {

                try {

                    if($this->{'get'.$this->getConfig()->getConfigKey()}() !== null) {

                        $sql = 'DELETE FROM '.$this->getConfig()->getConfigTableName();
                        $sql .= ' WHERE ('.$this->getConfig()->getConfigTableName().'.'.$this->getConfig()->getConfigData("Key").' = :id)';

                        // Preparamos la Consulta
                        $sth = $this->getConnection()->prepare($sql);

                        // Colocamos el Valor del Key para el WHERE
                        $tmpValue = $this->{'get'.$this->getConfig()->getConfigKey()}();
                        $sth->bindValue(':id', $tmpValue);

                        $this->getConnection()->beginTransaction();

                        // Ejecutamos la sentencia
                        if($sth->execute()) {

                            // Verificamos si realmente ya no existe el Objeto que se acaba de intentar Borrar
                            if ($this->exists() == null) {
                                // Coloca el Valor del Campo Key en NULL para evitar Errores si antes de destruir el objeto se ejecuta SAVE.
                                $this->{'set'.$this->getConfig()->getConfigKey()}(null);
                                $this->getConnection()->commit();
                                return true;
                            } else  { // El Registro no se Borro, por algun motivo se ejecuto bien la sentecia pero no se borro.
                                return false;
                            }
                        } else {
                            $this->Message()->setMessage('Persist - delete - Error: No se pudo ejecutar el DELETE.<br>'.$sql.'<br><pre>'.print_r($sth->errorInfo(), true).'</pre>');
                            $this->getConnection()->rollBack();
                            return false;
                        }

                    } else  { // No posee valor en el Atributo Key, por lo tanto no puedo comenzar a buscarlo.
                        $this->Message()->setMessage('No posee valor en el Atributo Key, por lo tanto no puedo comenzar a buscarlo.');
                        return false;
                    }

                } catch(PDOException $e)  {
                    $this->Message()->setMessage("Persist [".get_class($this)."] - ERROR: " . $e->getMessage() . "<br/>");
                    return false;
                }

            } else  {
                $this->Message()->setMessage("Persist Error: No estamos conectado con la Base de Datos.");
                return false;
            }
        }

        /**
        * Verifica si el objeto es Valido, es decir, si ya ha sido persistido, para ello verifica si posee ID, porque este valor solo es completado automaticamente luego de hace un find o save.
        *
        * @return boolean
        */
        public function isValid()	{
            Debug::captureBackTrace();
            // Buscamos el Key del Objeto y verificamos si su valor es NULL o no
            if (!is_null($this->{'get'.$this->getConfig()->getConfigKey()}()) ) {
                return true;
            } else {
           		return false;
            }
        }

    }

?>